package com.sfdk.net.php
{
import com.sfdk.events.RemoteEvent;
import com.sfdk.log.Logger;
import com.sfdk.net.php.messages.AbstractMessage;
import com.sfdk.net.php.messages.AcknowledgeMessage;
import com.sfdk.net.php.messages.AcknowledgeMessageExt;
import com.sfdk.net.php.messages.AsyncMessage;
import com.sfdk.net.php.messages.AsyncMessageExt;
import com.sfdk.net.php.messages.CommandMessage;
import com.sfdk.net.php.messages.CommandMessageExt;
import com.sfdk.net.php.messages.ErrorMessage;
import com.sfdk.net.php.messages.IMessage;
import com.sfdk.net.php.messages.RemotingMessage;
import com.sfdk.utils.Base64;

import flash.events.AsyncErrorEvent;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.net.Responder;
import flash.net.registerClassAlias;
import flash.utils.ByteArray;

public class RemotingService extends EventDispatcher
	{
		private var _gatewayUrl : String;
		private var _sessionID : String;
		private var _netConnection : NetConnection;
		private var _appendToURL : String;
		private var _initConnection : Boolean = true;
		
		private static var _instance : RemotingService;
		
		public function RemotingService( gatewayUrl : String ) 
		{
			if(_instance == null)
			{
				_gatewayUrl = gatewayUrl;
				init();
			}
		}
		
		public static function get instance() : RemotingService
		{
			return _instance;
		}
		
		private function init() : void
		{
			_netConnection = new NetConnection();
			_instance = this;
			
			_netConnection.client = this;
			_netConnection.objectEncoding = ObjectEncoding.AMF3;
			
			registerClassAlias("flex.messaging.messages.CommandMessage", CommandMessage);
			registerClassAlias("flex.messaging.messages.AsyncMessage", AsyncMessage);		
			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);	
			registerClassAlias("flex.messaging.messages.RemotingMessage", RemotingMessage);
			registerClassAlias("flex.messaging.messages.ErrorMessage", ErrorMessage);
			
			registerClassAlias("DSA", AsyncMessageExt);
			registerClassAlias("DSC", CommandMessageExt);
			registerClassAlias("DSK", AcknowledgeMessageExt);
			
			connectInstance();
		}
		
		
		private function connectInstance() : void
		{
			var url : String = _gatewayUrl;
			if(_appendToURL)
			{
				url += _appendToURL;
			}
			if (_netConnection.uri != null && _netConnection.uri.length > 0 && _netConnection.connected)
			{
				_netConnection.removeEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
				_netConnection.close();
			}
			
			_netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus, false, 0, true);
			_netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError, false, 0, true);	
			_netConnection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError, false, 0, true);
			_netConnection.addEventListener(IOErrorEvent.IO_ERROR, onIoError, false, 0, true);
			
			_netConnection.connect(url);
			
			if(_initConnection)
			{
				_initConnection = false;
				
				var msg:CommandMessage = new CommandMessage();
				msg.operation = CommandMessage.CLIENT_PING_OPERATION;
				msg.headers[CommandMessage.MESSAGING_VERSION] = 1.0;
				msg.headers[CommandMessage.NEEDS_CONFIG_HEADER] = true;
				
				msg.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER] = (_sessionID != null) ? _sessionID : "nil";
				var responder : Responder = new Responder(loginResult, loginFault);
				
				_netConnection.call(null, responder, msg);
			}
		}
		
		public function send(responder : Responder, message : IMessage) : void
		{
			message.clientId = (_sessionID != null) ? _sessionID : "nil";
			message.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER] = (_sessionID != null) ? _sessionID : "nil";
			
			_netConnection.call(null, responder, message);
		}
		public function login(username : String, password : String) : void
		{
			var credentials:String = null;
			if (username != null && password != null)
			{
				var rawCredentials:String = username + ":" + password;
				var encodedCredentials : ByteArray = new ByteArray();
				encodedCredentials.writeUTFBytes(rawCredentials);
				
				credentials = Base64.encode(encodedCredentials);
			}
			
			var msg:CommandMessage = new CommandMessage();
			msg.operation = CommandMessage.LOGIN_OPERATION;
			msg.body = credentials;
			msg.headers[CommandMessage.MESSAGING_VERSION] = 1.0;
			var responder : Responder = new Responder(loginResult, loginFault);
			
			send(responder, msg);
		}
		
		private function loginResult(msg : IMessage) : void
		{
			// Update the ServerConfig with dynamic configuration
			if (msg != null)
			{
				// Set the server assigned FlexClient Id.
				if (_sessionID == null && msg.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER] != null)
					_sessionID = msg.headers[AbstractMessage.FLEX_CLIENT_ID_HEADER];
			}
			
			handleReconnectWithSessionId();
		}
		
		private function loginFault(msg : ErrorMessage) : void
		{
			if (msg != null)
			{
				// An authentication fault means we reached it which
				// still means we can connect.
				if (msg.faultCode == "Client.Authentication")
				{
					loginResult(msg);
					dispatchEvent(new RemoteEvent(RemoteEvent.LOGIN_FAILED));
				}
				else
				{
					// Set the server assigned FlexClient Id.
					dispatchEvent(new RemoteEvent(RemoteEvent.SERVER_UNAVAILABLE));
				}
			}
			
			handleReconnectWithSessionId();
		}
		
		private function onNetStatus(event:NetStatusEvent):void 
		{
			dispatchEvent(new RemoteEvent(RemoteEvent.SERVER_UNAVAILABLE));
   			Logger.log("RemotingService :: NET_STATUS -> "+event);
		}
	
		private function onSecurityError(event:SecurityErrorEvent):void 
		{
			Logger.error("RemotingService :: SECURITY_ERROR -> "+event);
		}
		
		private function onAsyncError(event:AsyncErrorEvent):void 
		{
			Logger.error("RemotingService :: ASYNC_ERROR -> "+event);
		}
		
		private function onIoError(event:IOErrorEvent):void 
		{
			Logger.error("RemotingService :: IO_ERROR -> "+event);
		}
		
		/**
		 *  @private
		 *  Special handler for legacy AMF packet level header "AppendToGatewayUrl".
		 *  When we receive this header we assume the server detected that a session was
		 *  created but it believed the client could not accept its session cookie, so we
		 *  need to decorate the channel endpoint with the session id.
		 *
		 *  We do not modify the underlying endpoint property, however, as this session
		 *  is transient and should not apply if the channel is disconnected and re-connected
		 *  at some point in the future.
		 */
		public function AppendToGatewayUrl(value:String):void
		{
			if (value != null && value != "" && value != _appendToURL)
			{
				_appendToURL = value;
				_reconnectingWithSessionId = true;
			}
		}
		
		private var _reconnectingWithSessionId : Boolean;
		/**
		 *  @private
		 *  Used by result and fault handlers to update the url of the underlying
		 *  NetConnection with session id.
		 */
		protected function handleReconnectWithSessionId():void
		{
			if (_reconnectingWithSessionId)
			{
				_reconnectingWithSessionId = false;
				shutdownNetConnection();
				connectInstance(); // To avoid another ping request.
			}
		}
		
		/**
		 *  @private
		 *  Shuts down the underlying NetConnection for the channel.
		 */
		protected function shutdownNetConnection():void
		{
			_netConnection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			_netConnection.removeEventListener(IOErrorEvent.IO_ERROR, onIoError);
			_netConnection.removeEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
			_netConnection.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
			_netConnection.close();
		}

	}
}